package org.example.frw.exception;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.BooleanUtil;
import feign.FeignException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.example.frw.annotation.AppResponseFlag;
import org.example.frw.annotation.IgnoreRestControllerResponseAdvice;
import org.example.frw.response.AppResponseResult;
import org.example.frw.response.ResponseResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.MethodParameter;
import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.lang.reflect.UndeclaredThrowableException;
import java.util.List;
import java.util.Objects;

@RestControllerAdvice
public class ControllerExceptionHandler implements ResponseBodyAdvice<Object> {

    public static final String HAS_SERVER_ERROR = "has-server-error";
    public static final String SERVER_ERROR_CODE = "server-error-code";
    public static final String SERVER_ERROR_DOMAIN = "server-error-domain";
    public static final String SERVER_ERROR_MESSAAGE = "server-error-messaage";
    @Value("${spring.application.domain:${spring.application.name:unknown-spring-boot}}")
    private String domain;

    private final ErrorCodeHandler errorCodeHandler;

    public ControllerExceptionHandler(ErrorCodeHandler errorCodeHandler) {
        this.errorCodeHandler = errorCodeHandler;
    }

    private static final Logger LOGGER = LoggerFactory.getLogger(ControllerExceptionHandler.class);

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        // 方法没有IgnoreRestControllerResponseAdvice注解，且response不是ResponseResult类型时启用beforeBodyWrite
        return !returnType.hasMethodAnnotation(IgnoreRestControllerResponseAdvice.class)
                && !returnType.getParameterType().isAssignableFrom(AppResponseResult.class);
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        response.getHeaders().set("Content-Type","application/json");
        // 原生数据不要包裹统一的response体
        // "raw-data"只有在请求正式发送出去才会添加，如果请求无法顺利发送，没有"raw-data"
        HttpHeaders headers = request.getHeaders();
        if (headers.containsKey("raw-data")) {
            List<String> strings = headers.get("raw-data");
            if (CollUtil.isNotEmpty(strings) && BooleanUtil.toBoolean(strings.get(0))) {
                return body;
            }
        }
        // 如果发生错误返回错误
        boolean hasError = false;
        int code = 0;
        String domain = "";
        String msg = "";
        HttpHeaders rHeaders = response.getHeaders();
        if (rHeaders.containsKey(HAS_SERVER_ERROR)) {
            // 发生错误，提取错误信息
            List<String> strings = rHeaders.get(HAS_SERVER_ERROR);
            hasError = CollUtil.isNotEmpty(strings) && BooleanUtil.toBoolean(strings.get(0));

            if (hasError) {
                if (rHeaders.containsKey(SERVER_ERROR_CODE)) {
                    List<String> codes = rHeaders.get(SERVER_ERROR_CODE);
                    if (CollUtil.isNotEmpty(codes)) {
                        code = Integer.parseInt(codes.get(0));
                    }
                }

                if (rHeaders.containsKey(SERVER_ERROR_DOMAIN)) {
                    List<String> domains = rHeaders.get(SERVER_ERROR_DOMAIN);
                    if (CollUtil.isNotEmpty(domains)) {
                        domain = domains.get(0);
                    }
                }

                if (rHeaders.containsKey(SERVER_ERROR_MESSAAGE)) {
                    List<String> messages = rHeaders.get(SERVER_ERROR_MESSAAGE);
                    if (CollUtil.isNotEmpty(messages)) {
                        msg = messages.get(0);
                    }
                }
            }
        }
        // 没发生错误返回success
        // for app
        AppResponseFlag annotation = Objects.requireNonNull(returnType.getMethod()).getAnnotation(AppResponseFlag.class);
        if (annotation != null) {
            if (hasError) {
                // 发生错误返回错误，避免错误出现在结果字段中，因为body可能是ExceptionHandler处理过的错误
                return AppResponseResult.fail(code, domain, msg);
            }
            // 如果返回值是void类型，直接返回200状态信息
            if (returnType.getParameterType().isAssignableFrom(void.class)) {
                return AppResponseResult.success();
            }
            if (!(body instanceof AppResponseResult)) {
                return AppResponseResult.success(body);
            }
        } else {
            // 发生错误返回错误，避免错误出现在结果字段中，因为body可能是ExceptionHandler处理过的错误
            if (hasError) {
                return ResponseResult.fail(code, domain, msg);
            }
            // for server
            // 如果返回值是void类型，直接返回200状态信息
            if (returnType.getParameterType().isAssignableFrom(void.class)) {
                return ResponseResult.success();
            }
            if (!(body instanceof AppResponseResult)) {
                return ResponseResult.success(body);
            }
        }
        return body;
    }

    @ExceptionHandler(value = {UndeclaredThrowableException.class, InvalidParamException.class})
    public ResponseResult<Void> handleInvalidParamException(Exception e, HttpServletRequest request, HttpServletResponse response) {
        LOGGER.error("Invalid param error, url [{}]，message [{}]", request.getRequestURL().toString(), e.getMessage(), e);
        Exception actualException = e;
        if(e.getCause() instanceof InvalidParamException){
            actualException = (InvalidParamException) e.getCause();
        }
        Integer code = errorCodeHandler.getErrorCode(actualException);
        response.addHeader(HAS_SERVER_ERROR, "true");
        response.addHeader(SERVER_ERROR_CODE, String.valueOf(code));
        response.addHeader(SERVER_ERROR_DOMAIN, domain);
        response.addHeader(SERVER_ERROR_MESSAAGE, actualException.getMessage());
        return ResponseResult.fail(code, domain, actualException.getMessage());
    }

    @ExceptionHandler(FeignException.class)
    public ResponseResult<Void> handleFeignException(FeignException e, HttpServletRequest request, HttpServletResponse response) {
        LOGGER.error("Feign error, url [{}]，message [{}]", request.getRequestURL().toString(), e.getMessage(), e);
        Integer code = errorCodeHandler.getErrorCode(e);
        response.addHeader(HAS_SERVER_ERROR, "true");
        response.addHeader(SERVER_ERROR_CODE, String.valueOf(errorCodeHandler.getErrorCode(e)));
        response.addHeader(SERVER_ERROR_DOMAIN, domain);
        response.addHeader(SERVER_ERROR_MESSAAGE, e.getMessage());
        return ResponseResult.fail(code, domain, e.getMessage());
    }

    @ExceptionHandler(InvalidDataAccessResourceUsageException.class)
    public ResponseResult<Void> handleException(InvalidDataAccessResourceUsageException e, HttpServletRequest request, HttpServletResponse response) {
        LOGGER.error("DB error, url [{}]，message [{}]", request.getRequestURL().toString(), e.getMessage(), e);
        Integer code = errorCodeHandler.getErrorCode(e);
        response.addHeader(HAS_SERVER_ERROR, "true");
        response.addHeader(SERVER_ERROR_CODE, String.valueOf(code));
        response.addHeader(SERVER_ERROR_DOMAIN, domain);
        response.addHeader(SERVER_ERROR_MESSAAGE, "DB error");
        return ResponseResult.fail(code, domain, "DB error");
    }

    @ExceptionHandler(Exception.class)
    public ResponseResult<Void> handleException(Exception e, HttpServletRequest request, HttpServletResponse response) {
        LOGGER.error("Internal error, url [{}]，message [{}]", request.getRequestURL().toString(), e.getMessage(), e);
        Integer code = errorCodeHandler.getErrorCode(e);
        response.addHeader(HAS_SERVER_ERROR, "true");
        response.addHeader(SERVER_ERROR_CODE, String.valueOf(code));
        response.addHeader(SERVER_ERROR_DOMAIN, domain);
        response.addHeader(SERVER_ERROR_MESSAAGE, e.getMessage());
        return ResponseResult.fail(code, domain, e.getMessage());
    }

}